package disredis

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/google/uuid"
	"sync"
	"time"
)

type redisLock struct {
	name string
	timeout int
	l sync.Mutex
	pool *redis.Pool
	trylocknum int
	v string
}
type RedisLockIF interface {
	Lock() error
	UnLock()error
}

type Option func(s *redisLock)
//超时时间
func WithTimeOut(t int) Option {
	return func(s *redisLock) {
		s.timeout =t
	}
}
//尝试的次数
func WithTryLockNum(num int)Option  {
	return func(s *redisLock) {
		s.trylocknum =num
	}
}

//pool redis线程池
//key  setnx的key
func NewRedisLock(pool *redis.Pool, key string,opts ...Option) (RedisLockIF,error) {

	if pool==nil {
		return nil,fmt.Errorf("redis.pool is nil")
	}
	if len(key) == 0 {
		return nil,fmt.Errorf("key is nil")
	}
	a:=new(redisLock)
	for _,optionV:=range opts{
		optionV(a)
	}

	a.name=key
	a.pool=pool
	if a.timeout <10{
		a.timeout = 30
	}
	if a.trylocknum <10{
		a.trylocknum =60
	}
	u4 := uuid.New()
	s:=u4.String()
	a.v=fmt.Sprintf("%s",s)
	return a,nil
}
//加锁
func (p *redisLock)Lock() error{
	conn:=p.pool.Get()
	p.l.Lock()
	defer func() {
		conn.Close()
		p.l.Unlock()
	}()
	for i:=0;i<p.trylocknum;i++{
		//成功的时候 OK    NIL
		//失败的时候       redigo: nil returned
		_, err1 := redis.String(conn.Do("SET", p.name, p.v, "EX", p.timeout, "NX"))
		if err1 != nil {
			if err1==redis.ErrNil { //
				ttl1, e := redis.Int(conn.Do("TTL", p.name))
				if e!=nil {
					continue
				}
				if ttl1>0 {
					time.Sleep(time.Duration(ttl1*1000/100)*time.Millisecond)
				}
				if ttl1 == -1 { //ttl =-2表示key不存在 -1表示永久存在
					conn.Do("DEL", p.name)
					return fmt.Errorf("加锁失败")
				}

				if i>=p.trylocknum-1{
					return fmt.Errorf("尝试加锁失败")
				}
				continue

			}
			return err1

		}

		//get locker 获取到锁了
		return nil
	}
	return nil

}
//释放锁
func (p *redisLock)UnLock()  error{
	conn:=p.pool.Get()
	defer conn.Close()
	vt, err:= redis.String(conn.Do("get", p.name))
	if vt == p.v{
		fmt.Println("==>释放锁")
		conn.Do("Del", p.name)
	}
	return err
}

//初始化一个redispool
func GetRedispool(addreess string,pwd string)  *redis.Pool{
	var custoptions []redis.DialOption
	custoptions=append(custoptions,redis.DialPassword(pwd))
	pool := &redis.Pool{
		MaxIdle:     10,
		MaxActive:   20000,
		IdleTimeout: 10 * time.Second,
		Dial: func() (redis.Conn, error) {
			return redis.Dial("tcp", addreess,custoptions...)
		},
	}
	conn := pool.Get()
	defer conn.Close()
	_, err := conn.Do("ping")
	if err != nil {
		panic("redis is not start........\n")
	}else{
		fmt.Println("redis starting success....")
	}
	return pool
}

